/********************************************************************/
/*																	*/
/*  ControlSocket.cpp												*/
/*																	*/
/*  Implementation of the CControlSocket class.						*/
/*	This class is a part of the CConnectThread which handles		*/
/*  socket connections. Incomming data is processed in OnReceive	*/
/*																	*/
/*  Programmed by Pablo van der Meer								*/
/*																	*/
/*  Copyright Pablo Software Solutions 2005							*/
/*	http://www.pablosoftwaresolutions.com							*/
/*																	*/
/*  Last updated: May 28, 2005										*/
/*																	*/
/********************************************************************/

#include "stdafx.h"
#include "FTPServerApp.h"
#include "FTPServer.h"
#include "ControlSocket.h"
#include "ConnectThread.h"
#include "ApplicationDlg.h"
#include "DataSocket.h"

extern CFTPServer theServer;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/********************************************************************/
/*																	*/
/* Function name : CControlSocket::CControlSocket					*/
/* Description   : Constructor										*/
/*																	*/
/********************************************************************/
CControlSocket::CControlSocket()
{
	m_nStatus = STATUS_LOGIN;
	m_pDataSocket = NULL;
	m_strRemoteHost = "";
	m_nRemotePort = -1;
	m_bPassiveMode = FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : CControlSocket::~CControlSocket					*/
/* Description   : Destructor										*/
/*																	*/
/********************************************************************/
CControlSocket::~CControlSocket()
{
	DestroyDataConnection();

	// tell our thread we have been closed
	AfxGetThread()->PostThreadMessage(WM_QUIT,0,0);
}


// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CControlSocket, CSocket)
	//{{AFX_MSG_MAP(CControlSocket)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif	// 0


/********************************************************************/
/*																	*/
/* Function name : OnClose											*/		
/* Description   : Send WM_QUIT message to the thread containing	*/
/*				   the socket to shutdown once the connection is	*/
/*                 closed.											*/
/*																	*/
/********************************************************************/
void CControlSocket::OnClose(int nErrorCode) 
{
	Close();
	// destroy connection
	m_pThread->PostThreadMessage(WM_QUIT, 0, 0);

	CSocket::OnClose(nErrorCode);
}


#define BUFFERSIZE 4096

/********************************************************************/
/*																	*/
/* Function name : OnReceive										*/		
/* Description   : Called by the framework to notify this socket	*/
/*                 that there is data in the buffer.				*/
/*																	*/
/********************************************************************/
void CControlSocket::OnReceive(int nErrorCode) 
{
	TCHAR buff[BUFFERSIZE+1];

	int nRead = Receive(buff, BUFFERSIZE);
	switch (nRead)
	{
		case 0:
			Close();
			break;

		case SOCKET_ERROR:
			if (GetLastError() != WSAEWOULDBLOCK) 
			{
				TCHAR szError[256];
				wsprintf(szError, "OnReceive error: %d", GetLastError());
				
				AfxMessageBox (szError);
			}
			break;

		default:
			if (nRead != SOCKET_ERROR && nRead != 0)
			{
				((CConnectThread *)AfxGetThread())->IncReceivedBytes(nRead);

				// terminate the string
				buff[nRead] = 0; 
				m_RxBuffer += CString(buff);
				
				GetCommandLine();
			}	
			break;
	}
	CSocket::OnReceive(nErrorCode);
}


/********************************************************************/
/*																	*/
/* Function name: GetCommandLine									*/		
/* Description  : Parse complete command line						*/
/*																	*/
/********************************************************************/
void CControlSocket::GetCommandLine()
{
	CString strTemp;
	int nIndex;

	while(!m_RxBuffer.IsEmpty())
	{
		nIndex = m_RxBuffer.Find("\r\n");
		if (nIndex != -1)
		{
			strTemp = m_RxBuffer.Left(nIndex);
			m_RxBuffer = m_RxBuffer.Mid(nIndex + 2);
			if (!strTemp.IsEmpty())
			{
				m_strCommands.AddTail(strTemp);
				// parse and execute command
				ProcessCommand();
			}
		}
		else
			break;
	}
}


/********************************************************************/
/*																	*/
/* Function name: GetCommand										*/		
/* Description  : Get command from receiver buffer.					*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetCommand(CString &strCommand, CString &strArguments)
{
	if (!m_strCommands.IsEmpty())
	{
		CString strBuff = m_strCommands.RemoveHead();

		// log command line
		PostStatusMessage(strBuff, 1);

		int nIndex = strBuff.Find(" ");
		if (nIndex != -1)
		{
			strCommand = strBuff.Left(nIndex);
			strArguments = strBuff.Mid(nIndex+1);
		}
		else
		{
			strCommand = strBuff;
		}

		if (!strCommand.IsEmpty())
		{
			strCommand.MakeUpper();

			// who screwed up ???
			if (strCommand.Right(4) == "ABOR")
			{
				strCommand = "ABOR";
			}
			return TRUE;
		}
	}
	return FALSE;
}


/********************************************************************/
/*																	*/
/* Function name: SendResponse										*/		
/* Description  : Send response to client.							*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::SendResponse(LPCTSTR pstrFormat, ...)
{
	CString str;

	// format arguments and put them in CString
	va_list args;
	va_start(args, pstrFormat);
	str.FormatV(pstrFormat, args);

	int nBytes = CSocket::Send(str + "\r\n", str.GetLength()+2);
	if (nBytes == SOCKET_ERROR)
	{
		Close();
		PostStatusMessage("Could not send reply, disconnected.", 2);	

		// tell our thread we have been closed
		m_pThread->PostThreadMessage(WM_QUIT, 1, 0);

		return FALSE;
	}

	PostStatusMessage(str, 2);

	((CConnectThread *)AfxGetThread())->IncSentBytes(nBytes);
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name: ProcessCommand									*/
/* Description  : Parse and execute command from client.			*/
/*																	*/
/********************************************************************/
void CControlSocket::ProcessCommand()
{
	CString strCommand, strArguments;

	// get command
	CString strBuff = m_strCommands.RemoveHead();
	int nIndex = strBuff.Find(" ");
	if (nIndex == -1)
	{
		strCommand = strBuff;
	}
	else
	{
		strCommand = strBuff.Left(nIndex);
		strArguments = strBuff.Mid(nIndex+1);
	}
	strCommand.MakeUpper();
	
	// log command line
	PostStatusMessage(strCommand + " " + strArguments, 1);

	if ((m_nStatus == STATUS_LOGIN) && (strCommand != "USER" && strCommand != "PASS"))
	{
		SendResponse("530 Please login with USER and PASS.");
		return;
	}
	
	// username entered
	if (strCommand == "USER")
	{
		m_nStatus = STATUS_LOGIN;
		m_strUserName = strArguments;

		CString strPeerAddress;
		UINT nPeerPort;
		GetPeerName(strPeerAddress, nPeerPort);

		// tell FTP server a new user has connected
		CConnectThread *pThread = (CConnectThread *)m_pThread;
		((CFTPServer *)pThread->m_pWndServer)->m_pEventSink->OnFTPUserConnected(m_pThread->m_nThreadID, m_strUserName, strPeerAddress);

		SendResponse("331 Password required for %s", m_strUserName);
	}
	else
	// password entered
	if (strCommand == "PASS")
	{
		// already logged on ?
		if (m_strUserName.IsEmpty())
		{
			SendResponse("503 Login with USER first.");
		}
		else
		{
			// now we have user name and password, attempt to login the client
			if (theServer.m_UserManager.GetUser(m_strUserName, m_User))
			{
				// check password
				if ((!m_User.m_strPassword.Compare(strArguments) || m_User.m_strPassword.IsEmpty()) && !m_User.m_bAccountDisabled)
				{
					// set home directory of user
					m_strCurrentDir = "/";

					// succesfully logged on
					m_nStatus = STATUS_IDLE;
					SendResponse("230 User successfully logged in.");
					return;
				}
			}
			SendResponse("530 Not logged in, user or password incorrect!");
		}
	}		
	else
	// close connection
	if ((strCommand == "QUIT") || (strCommand == "BYE"))
	{
		// send goodbye message to client
		CConnectThread *pThread = (CConnectThread *)m_pThread;
		SendResponse("220 %s", ((CFTPServer *)pThread->m_pWndServer)->GetGoodbyeMessage());

		Close();
		
		// tell our thread we have been closed
	
		// destroy connection
		m_pThread->PostThreadMessage(WM_QUIT, 1, 0);
	}
	else
	// change transfer type
	if (strCommand == "TYPE")
	{
		// let's pretend we did something...
		SendResponse("200 Type set to %s", strArguments);
	}
	else
	// print current directory
	if (strCommand == "PWD")
	{
		SendResponse("257 \"%s\" is current directory.", m_strCurrentDir);
	}
	else
	// change to parent directory
	if (strCommand == "CDUP")
	{
		DoChangeDirectory("..");
	}
	else
	// change working directory
	if (strCommand == "CWD")
	{
		DoChangeDirectory(strArguments);
	}
	else
	// specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2. 
	if (strCommand == "PORT")
	{
		CString strSub;
		int nCount=0;

		while (AfxExtractSubString(strSub, strArguments, nCount++, ','))
		{
			switch(nCount)
			{
				case 1:	// a1
					m_strRemoteHost = strSub;
					m_strRemoteHost += ".";
					break;
				case 2:	// a2
					m_strRemoteHost += strSub;
					m_strRemoteHost += ".";
					break;
				case 3:	// a3
					m_strRemoteHost += strSub;
					m_strRemoteHost += ".";
					break;
				case 4:	// a4
					m_strRemoteHost += strSub;
					break;
				case 5:	// p1
					m_nRemotePort = 256*atoi(strSub);
					break;
				case 6:	// p2
					m_nRemotePort += atoi(strSub);
					break;
			}
		}
		SendResponse("200 Port command successful.");
	}
	else
	// switch to passive mode
	if (strCommand == "PASV")
	{	
		// delete existing datasocket
		DestroyDataConnection();

		// create new data socket
		m_pDataSocket = new CDataSocket(this);

		if (!m_pDataSocket->Create())
		{
			DestroyDataConnection();	
			SendResponse("421 Failed to create socket.");
			return;
		}
		// start listening
		m_pDataSocket->Listen();
		m_pDataSocket->AsyncSelect();
		
		CString strIPAddress, strTmp;
		UINT nPort;
		
		// get our ip address
		GetSockName(strIPAddress, nPort);
		// get listen port
		m_pDataSocket->GetSockName(strTmp, nPort);
		// replace dots 
		strIPAddress.Replace(".", ",");
		// tell the client which address/port to connect to
		SendResponse("227 Entering Passive Mode (%s,%d,%d).", strIPAddress, nPort/256, nPort%256);
		
		m_bPassiveMode = TRUE;
	} 
	else
	// list current directory (or a specified file/directory)
	if (strCommand == "LIST")
	{
		CString strListing;
		if (!GetDirectoryList(strArguments, strListing))
		{
			// something went wrong
			return;
		}

		SendResponse("150 Opening ASCII mode data connection for directory list."); 

		// create data connection with client
		if (CreateDataConnection())
		{
			if (strListing.IsEmpty())
			{
				// close data connection with client
				DestroyDataConnection();

				SendResponse("226 Transfer complete."); 
				m_nStatus = STATUS_IDLE;
				return;
			}
		}
		else
		{
			// close data connection with client
			DestroyDataConnection();
			return;
		}

		m_nStatus = STATUS_LIST;
		
		// send the listing
		m_pDataSocket->SendListing(strListing);
	} 
	else
	// retrieve file
	if (strCommand == "RETR")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DOWNLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
				SendResponse("150 Opening BINARY mode data connection for file transfer.");

				// create socket connection for file transfer
				if (!CreateDataConnection())
				{
					m_nStatus = STATUS_IDLE;
					// close data connection with client
					DestroyDataConnection();
				}
				else
				{
					m_nStatus = STATUS_DOWNLOAD;
					// send the file
					m_pDataSocket->SendFile(strResult);
				}
				break;
		}
	}
	else
	// client wants to upload file
	if (strCommand == "STOR")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_UPLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 Filename invalid.");
				break;
			default:
				SendResponse("150 Opening BINARY mode data connection for file transfer.");

				// create socket connection for file transfer
				if (!CreateDataConnection())
				{
					m_nStatus = STATUS_IDLE;
					// close data connection with client
					DestroyDataConnection();
				}
				else
				{
					m_nStatus = STATUS_UPLOAD;
					// retrieve the file
					m_pDataSocket->RetrieveFile(strResult);
				}
				break;
		}
	}
	else
	// get file size
	if (strCommand == "SIZE")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DOWNLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
			{
				CFileStatus status;
				CFile::GetStatus(strResult, status);
				SendResponse("213 %d", status.m_size);
				break;
			}
		}
	}
	else
	// delete file
	if (strCommand == "DELE")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DELETE, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
				CString strRelativePath;
				GetRelativePath(strResult, strRelativePath);

				// delete the file
				if (!DeleteFile(strResult))
				{
					SendResponse("450 Internal error deleting the file: \"%s\".",  strRelativePath);
				}
				else
				{
					SendResponse("250 File \"%s\" was deleted successfully.", strRelativePath);
				}
				break;
		}
	}
	else
	// remove directory
	if (strCommand == "RMD")
	{		
		CString strResult;
		int nResult = CheckDirectory(strArguments, FTP_DELETE, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_PATH_NOT_FOUND:
				SendResponse("550 Directory not found.");
				break;
			default:
				// remove the directory
				if (!RemoveDirectory(strResult))
				{
					if (GetLastError() == ERROR_DIR_NOT_EMPTY)
					{
						SendResponse("550 Directory not empty.");
					}
					else
					{
						SendResponse("450 Internal error deleting the directory.");
					}
				}
				else
				{
					SendResponse("250 Directory deleted successfully.");
				}
				break;
		}
	}
	else
	// create directory
	if (strCommand == "MKD")
	{		
		CString strResult;
		int nResult = CheckDirectory(strArguments, FTP_CREATE_DIR, strResult);
		switch(nResult)
		{
			case ERROR_SUCCESS:
				SendResponse("550 Directory already exists.");
				break;
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Can't create directory. Permission denied.");
				break;
			default:
				// create directory structure
				if (!MakeSureDirectoryPathExists(strResult))
				{
					SendResponse("450 Internal error creating the directory.");
				}
				else
				{
					SendResponse("250 Directory created successfully.");
				}
				break;
		}
	}
	else
	// abort transfer
	if (strCommand == "ABOR")
	{		
		if (m_pDataSocket)
		{
			if (m_nStatus != STATUS_IDLE)
			{
				SendResponse("426 Data connection closed.");
			}
			// destroy data connection
			m_pThread->PostThreadMessage(WM_DESTROYDATACONNECTION, 0, 0);
		}
		SendResponse("226 ABOR command successful.");
	} 
	else
	// get system info
	if (strCommand == "SYST")
	{
		SendResponse("215 UNIX emulated by Quick 'n Easy FTP Server.");
	}
	else
	// dummy instruction
	if (strCommand == "NOOP")
	{
		SendResponse("200 OK");
	}
	else
	{
		SendResponse("502 Command not implemented.");
	}
}


/********************************************************************/
/*																	*/
/* Function name: PostStatusMessage									*/		
/* Description  : Post status message.								*/
/*																	*/
/********************************************************************/
void CControlSocket::PostStatusMessage(LPCTSTR lpszStatus, int nType)
{
	CConnectThread *pThread = (CConnectThread *)m_pThread;
	((CFTPServer *)pThread->m_pWndServer)->AddTraceLine(nType, "[%u] %s", m_pThread->m_nThreadID, lpszStatus);
}


/********************************************************************/
/*																	*/
/* Function name: CreateDataConnection								*/		
/* Description  : Create data transfer connection.					*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::CreateDataConnection()
{
	if (!m_bPassiveMode)
	{
		// need PORT command first !
		if (m_strRemoteHost == "" || m_nRemotePort == -1)
		{
			SendResponse("425 Can't open data connection.");
			return FALSE;
		}

		m_pDataSocket = new CDataSocket(this);
		if (m_pDataSocket->Create())
		{
			m_pDataSocket->AsyncSelect();

			// connect to remote site
			if (m_pDataSocket->Connect(m_strRemoteHost, m_nRemotePort) == 0)
			{
				if (GetLastError() != WSAEWOULDBLOCK)
				{
					SendResponse("425 Can't open data connection.");
					return FALSE;
				}
			}
		}
		else
		{
			SendResponse("421 Failed to create data connection socket.");
			return FALSE;
		}
	}

	// wait until we're connected (for max 10 seconds)
	DWORD dwTickCount = GetTickCount() + 10000;
	while (!m_pDataSocket->m_bConnected)
	{
		DoEvents();
		if (dwTickCount < GetTickCount())
		{
			SendResponse("421 Failed to create data connection socket.");
			return FALSE;
		}
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name: DestroyDataConnection								*/		
/* Description  : Close data transfer connection.					*/
/*																	*/
/********************************************************************/
void CControlSocket::DestroyDataConnection()
{
	if (!m_pDataSocket)
		return;

	delete m_pDataSocket;

	// reset transfer status
	m_pDataSocket = NULL;
	m_strRemoteHost = "";
	m_nRemotePort = -1;
	m_bPassiveMode = FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : GetLocalPath										*/
/* Description   : Convert relative path to local path				*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetLocalPath(LPCTSTR lpszRelativePath, CString &strLocalPath)
{
	CString strRelativePath = lpszRelativePath;

	// make unix style
	strRelativePath.Replace("\\","/");
	while(strRelativePath.Replace("//","/"));

	CStringList partList;
	CString strSub;
	int nCount=0;
	BOOL bPathExists = TRUE;

	// split path in parts
	while(AfxExtractSubString(strSub, strRelativePath, nCount++, '/'))
	{
		// get rid of insignificant dots
		if (strSub != "..")
		{
			strSub.TrimLeft('.');
			strSub.TrimRight('.');
		}

		if (!strSub.IsEmpty())
			partList.AddTail(strSub);
	}
	
	// search for home directory
	CString strHomeDir;
	if (lpszRelativePath[0] == '/')
	{
		// absolute path
		strHomeDir = m_User.m_strHomeDirectory;
	}
	else
	{
		// relative path
		GetLocalPath(m_strCurrentDir, strHomeDir);
	}

	while(!partList.IsEmpty())
	{
		CString strPart = partList.GetHead();
		partList.RemoveHead();

		CString strCheckDir;
		
		if (strPart == "..")
		{
			// go back one level
			int nPos = strHomeDir.ReverseFind('\\');
			if (nPos != -1)
			{
				strCheckDir = strHomeDir.Left(nPos);
			}
		}
		else
		{
			strCheckDir = strHomeDir + "\\" + strPart;
		}
	
		// does directory exist ?
		if (FileExists(strCheckDir, TRUE))
		{
			strHomeDir = strCheckDir;
		}
		else
		// does file exist ?
		if (FileExists(strCheckDir, FALSE))
		{
			strHomeDir = strCheckDir;
		}
		else
		{
			strHomeDir = strCheckDir;

			// directory not found
			bPathExists = FALSE;
		}
	}
	
	// successfully converted directory
	strLocalPath = strHomeDir;

	// fix for root drive
	if (strLocalPath.Right(1) == ':')
		strLocalPath += "\\";

	return bPathExists;
}


/********************************************************************/
/*																	*/
/* Function name : GetRelativePath									*/		
/* Description   : Convert local path to relative path 				*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetRelativePath(LPCTSTR lpszLocalPath, CString &strRelativePath)
{
	CString strOffset = m_User.m_strHomeDirectory;
	CString strLocalPath = lpszLocalPath;

	strOffset.MakeLower();
	strLocalPath.MakeLower();

	if (strOffset.Right(1) != '\\')
		strOffset += "\\";

	if (strLocalPath.Right(1) != '\\')
		strLocalPath += "\\";

	if (strOffset == strLocalPath)
	{
		strRelativePath = "/";
	}
	else
	{
		strRelativePath = strLocalPath;
		strRelativePath.Replace(strOffset, "/");
		strRelativePath.Replace("\\", "/");
		strRelativePath.TrimRight('/');
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name : CheckAccessRights								*/
/* Description   : Check if user has access to specified directory.	*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::CheckAccessRights(LPCTSTR lpszDirectory, int nOption)
{
	// start with full path
	CString strCheckDir = lpszDirectory;

	while(!strCheckDir.IsEmpty())
	{
		CString strPath1 = strCheckDir;
		strPath1.TrimRight("\\");
		CString strPath2 = m_User.m_strHomeDirectory;
		strPath2.TrimRight("\\");

		// found a match ?
		if (strPath1.CompareNoCase(strPath2) == 0)
		{
			// check file access rights
			if (((!m_User.m_bAllowDownload) && (nOption == FTP_DOWNLOAD)) ||
				((!m_User.m_bAllowUpload) && (nOption == FTP_UPLOAD)) ||
				((!m_User.m_bAllowRename) && (nOption == FTP_RENAME)) ||
				((!m_User.m_bAllowDelete) && (nOption == FTP_DELETE)) ||
				((!m_User.m_bAllowCreateDirectory) && (nOption == FTP_CREATE_DIR)) ||
				((!m_User.m_bAllowDownload && !m_User.m_bAllowUpload) && (nOption == FTP_LIST)))
			{
				return FALSE;
			}
			return TRUE;
		}
		int nPos = strCheckDir.ReverseFind('\\');
		if (nPos != -1)
		{
			// strip subdir 
			strCheckDir = strCheckDir.Left(nPos);
		}
		else
		{
			// we're done
			strCheckDir.Empty();
		}
	} 
	// users has no rights to this directory
	return FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : GetDirectoryList									*/
/* Description   : Get directory listing for specfied directory		*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetDirectoryList(LPCTSTR lpszDirectory, CString &strResult)
{
	CString strDirectory = lpszDirectory;

	// clear listing
	strResult = "";

	// just return current directory
	strDirectory.TrimRight('*');

	// if client did not specify a directory -> use current dir
	if (strDirectory.IsEmpty())
	{
		strDirectory = m_strCurrentDir;
	}

	CString strLocalPath;
	int nResult = CheckDirectory(strDirectory, FTP_LIST, strLocalPath);
	switch(nResult)
	{
		case ERROR_ACCESS_DENIED:
			// user has no permissions
			SendResponse("550 \"%s\": Permission denied.", lpszDirectory);
			return FALSE;

		case ERROR_PATH_NOT_FOUND:
			// unable to convert to local path
			SendResponse("550 \"%s\": Directory not found.", lpszDirectory);
			return FALSE;

		default:
			// everything is successful
			break;
	}

	CFileFind find;
	BOOL bFound = FALSE;

	// check if it's a directory
	if ((GetFileAttributes(strLocalPath) & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
	{
		CString strPath = strLocalPath;
		if (strPath.Right(1)==_T("\\"))
			bFound = find.FindFile(strLocalPath + "*.*");
		else
			bFound = find.FindFile(strLocalPath + "\\*.*");
	}
	else
	{
		// it's a file
		bFound = find.FindFile(strLocalPath);
	}

	while (bFound)
	{
		bFound = find.FindNextFile();
		
		// skip "." and ".." 
		if (find.IsDots())
			continue;

		// permissions
		if (find.IsDirectory())
			strResult += "drwx------";
		else
			strResult += "-rwx------";

		// groups
		strResult += " 1 user group ";
		// file size
		CString strLength;
		strLength.Format("%d", find.GetLength());
		CString strFiller = "              ";
		strResult += strFiller.Left(strFiller.GetLength() - strLength.GetLength());
		strResult += strLength;
		// file date
		strResult += GetFileDate(find);
		// file name
		strResult += find.GetFileName();
		// end of line
		strResult += "\r\n";
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name : CheckFileName									*/
/* Description   : Check if filename is valid and if user has		*/
/*				   access rights.									*/
/*																	*/
/********************************************************************/
int CControlSocket::CheckFileName(CString strFilename, int nOption, CString &strResult)
{
	// make unix style
	strFilename.Replace("\\", "/");
	while(strFilename.Replace("//", "/"));
	strFilename.TrimRight("/");
	
	if (strFilename == "")
	{
		// no file name
		return ERROR_FILE_NOT_FOUND;
	}

	// append filename to directory
	CString strDirectory = m_strCurrentDir;

	// client has specified complete path 
	int nPos = strFilename.ReverseFind('/');
	if (nPos != -1)
	{
		strDirectory = strFilename.Left(nPos);
		if (strDirectory == "")
			strDirectory = "/";
		strFilename = strFilename.Mid(nPos+1);
	}

	// get local path
	CString strLocalPath;
	if (!GetLocalPath(strDirectory, strLocalPath))
	{
		// directory does not exist
		return ERROR_FILE_NOT_FOUND;
	}

	// create the complete path
	strResult = strLocalPath + "\\" + strFilename;

	if ((nOption != FTP_UPLOAD) && !FileExists(strResult, FALSE))
	{
		// file does not exist
		return ERROR_FILE_NOT_FOUND;
	}

	// check file access rights
	if (!CheckAccessRights(strLocalPath, nOption))
	{
		// user has no permissions, to execute specified action
		return ERROR_ACCESS_DENIED;
	}
	
	// everything is ok
	return ERROR_SUCCESS;
}


/********************************************************************/
/*																	*/
/* Function name : CheckDirectory									*/
/* Description   : Check if directory exists and if user has		*/
/*				   appropriate permissions.							*/
/*																	*/
/********************************************************************/
int CControlSocket::CheckDirectory(CString strDirectory, int nOption, CString &strResult)
{
	// make unix compatible
	strDirectory.Replace("\\","/");
	while(strDirectory.Replace("//","/"));
	strDirectory.TrimRight("/");
	
	if (strDirectory.IsEmpty())
	{
		if (nOption == FTP_LIST)
		{
			strDirectory = "/";
		}
		else
		{
			// no directory
			return ERROR_PATH_NOT_FOUND;
		}
	}
	else
	{
		// first character '/' ?
		if (strDirectory.Left(1) != "/")
		{ 
			// client specified a path relative to their current path
			if (m_strCurrentDir.Right(1) != '/')
				strDirectory = m_strCurrentDir + "/" + strDirectory;
			else
				strDirectory = m_strCurrentDir + strDirectory;
		}
	}

	// does directory exist ?
	BOOL bPathExists = GetLocalPath(strDirectory, strResult);
		
	if (!bPathExists && (nOption != FTP_CREATE_DIR))
	{ 
		return ERROR_PATH_NOT_FOUND;
	}

	// check directory access rights
	if (!CheckAccessRights(strResult, nOption))
	{
		// user has no permissions, to execute specified action
		return ERROR_ACCESS_DENIED;
	}

	// did directory exist ?
	if (!bPathExists)
		return ERROR_PATH_NOT_FOUND;

	// function successfull
	return ERROR_SUCCESS;
}


/********************************************************************/
/*																	*/
/* Function name : DoChangeDirectory								*/
/* Description   : Change to specified directory.					*/
/*																	*/
/********************************************************************/
void CControlSocket::DoChangeDirectory(LPCTSTR lpszDirectory)
{
	// try to change to specified directory
	CString strLocalPath;
	int nResult = CheckDirectory(lpszDirectory, FTP_LIST, strLocalPath);
	switch(nResult)
	{
		case ERROR_ACCESS_DENIED:
			// user has no permissions
			SendResponse("550 \"%s\": Permission denied.", lpszDirectory);
			break;
		case ERROR_PATH_NOT_FOUND:
			// unable to convert to local path
			SendResponse("550 \"%s\": Directory not found.", lpszDirectory);
			break;
		default:
			// everything is successful
			CString strRelativePath;
			if (GetRelativePath(strLocalPath, strRelativePath))
			{
				m_strCurrentDir = strRelativePath;
			}
			SendResponse("250 \"%s\" is current directory.", m_strCurrentDir);
			break;
	}
}


